﻿using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace SyncUdp.Network
{
    using SyncUdp.Enum;
    using SyncUdp.Event.NetworkEvent;
    using SyncUdp.Interface;
    using SyncUdp.Model;
    using SyncUdp.Utility;
    using System;
    using System.Linq;
    using System.Net;
    using System.Net.Sockets;
    using System.Threading;
    using System.Threading.Tasks;

    /// <summary>
    /// 基于 UDP 的服务器
    /// </summary>
    public class GameUdpServer : AbstractGameNetwork
    {
        /// <summary>
        /// 已连接的客户端
        /// </summary>
        Dictionary<int, PlayerData> _connectedClients;

        /// <summary>
        /// 需要补发帧的客户端
        /// </summary>
        Dictionary<IPEndPoint, int> _need2ReissueClients;

        List<IPEndPoint> _need2Remove;

        /// <summary>
        /// 用于检测客户端是否断连，接收到客户端的输入则在此字段存放
        /// </summary>
        HashSet<IPEndPoint> _clientsToCheck;

        UdpServerModel _model;

        /// <summary>
        /// 依赖于 .Net 的信号量，用于操控线程执行顺序
        /// </summary>
        SemaphoreSlim _semaphore;

        byte[] _checkDisconnectionMsgBuffer;
        int _maxPlayersCount;

        /// <summary>
        /// 基于 UDP 服务器的构造器
        /// </summary>
        public GameUdpServer(IPEndPoint ep, int maxPlayersCount, string logFileName = "Server") 
            : base(ep, logFileName)
        {
            _maxPlayersCount = maxPlayersCount;
            _connectedClients = new Dictionary<int, PlayerData>(_maxPlayersCount);
            _need2ReissueClients = new Dictionary<IPEndPoint, int>(_maxPlayersCount);
            _need2Remove = new List<IPEndPoint>();
            _clientsToCheck = new HashSet<IPEndPoint>(_maxPlayersCount);
            _semaphore = new SemaphoreSlim(1);
            // 存放 10 分钟的帧数
            var gm = IOCContainer.GetInstance<IGameManager>();
            _model = new UdpServerModel((int)(10 * 60 * gm.CommonData.serverFPS));
        }

        public override void Start(IGameManager gm)
        {
            base.Start();
            _logUtility.Logging($"1. GameUdpServer：开启定时器用于分发输入");
            SetTimer(gm.CommonData.serverDispatchesTime, DispatchPlayerInputs);
            _logUtility.Logging($"1. GameUdpServer：开启定时器用于检查断连");
            SetTimer(gm.CommonData.serverCheckDisconnectionTime, CheckDisconnection);
        }

        #region 服务器操作
        void SetTimer(float interval, Action action)
        {
            Task.Run(async () =>
            {
                int miliseconds = (int)(interval * 1000);
                while (_cts.IsCancellationRequested == false)
                {
                    await Task.Delay(miliseconds);
                    action?.Invoke();
                }
            }).ContinueWith(task =>
            {
                _logUtility.Logging($"1. GameUdpServer 定时器错误：{GetType()}{Environment.NewLine}" +
                    $"{task.Exception.InnerException}");
            }, TaskContinuationOptions.OnlyOnFaulted);
        }

        /// <summary>
        /// 给其他人发送名字和 ID
        /// </summary>
        [Obsolete]
        async Task SendNameAndIdToOthers(JoinGameEvent e)
        {            
            // 如果没有人则跳过这些步骤
            if (_connectedClients.Count == 0)
            {
                _logUtility.Logging($"1. GameUdpServer 给其他玩家转发新客户端时，没有其它客户端可以通知。");
                return;
            }
            _logUtility.Logging($"1. GameUdpServer 开始给其他玩家转发新客户端……");
            if (_networkEventField.joinGameEventArgs == null)
                _networkEventField.joinGameEventArgs = new JoinGameEvent();
            var args = _networkEventField.joinGameEventArgs;
            args.playerId = e.playerId;
            //args.playerName = e.playerName;
            await Task.Delay(500);
            _logUtility.Logging($"1. GameUdpServer 给其他玩家转发新客户端完毕！");
        }

        void DispatchPlayerInputs()
        {
            if (_networkEventField.serverDispathesEventArgs == null)
                _networkEventField.serverDispathesEventArgs = new ServerDispathesInputsEvent();
            if (_networkEventField.serverReissuesFramesEventArgs == null)
                _networkEventField.serverReissuesFramesEventArgs = new ServerReissuesFramesEvent();
            var t = Task.Run(async () =>
            {
                var args = _networkEventField.serverDispathesEventArgs;
                args.serverFrame = _model.CurFrame;
                args.allClientInputs = _model.AllOptionsOnCurFrame;
                // 使用同步基元来保证分发时，字典不会被修改
                _semaphore.Wait();
                foreach (var data in _connectedClients.Values)
                {
                    var item = args.allClientInputs.Find(input => input.id == data.id);
                    if (item == null)
                        args.allClientInputs.Add(new ClientInputsEvent() 
                        { 
                            id = data.id, 
                            name = data.name,
                            isDisconnected = data.isDisconnected, 
                        });
                    else
                        item.isDisconnected = data.isDisconnected;
                }
                var networkEventMsg = new NetworkEventToSend()
                {
                    eventType = NetworkEventType.ServerDispatchesInputs,
                    serverDispathesEventArgs = args,
                };
                var buffer = _jsonUtility.GetBytesFromObjectWithUTF8(networkEventMsg);
                foreach (var client in _connectedClients)
                {
                    // 既没有断连，也不用补发，才能向该客户端发送输入
                    bool cannot = client.Value.isDisconnected ||
                        _need2ReissueClients.ContainsKey(client.Value.ep);
                    if (cannot == false)
                        await _udpClient.SendAsync(buffer, buffer.Length, client.Value.ep);
                }
                // 补发帧数
                networkEventMsg.eventType = NetworkEventType.ServerReissuesInputs;
                networkEventMsg.serverDispathesEventArgs = null;
                _need2Remove.Clear();
                foreach (var client in _need2ReissueClients)
                {
                    var inputs = _model.GetInputsFromFrame2CurFrame(client.Value);
                    _networkEventField.serverReissuesFramesEventArgs.reissuedInputs = inputs;
                    networkEventMsg.serverReissuesFramesEventArgs = _networkEventField.serverReissuesFramesEventArgs;
                    buffer = _jsonUtility.GetBytesFromObjectWithUTF8(networkEventMsg);
                    await _udpClient.SendAsync(buffer, buffer.Length, client.Key);
                    if (inputs.Count < 100)
                        _need2Remove.Add(client.Key);
                }
                foreach (var client in _need2Remove)
                    _need2ReissueClients.Remove(client);
                _semaphore.Release();
                _model.CurFrame++;
            });
            t.ContinueWith(task =>
            {
                _logUtility.Logging($"1. GameUdpServer 分发输入时错误：{Environment.NewLine}" +
                    $"{task.Exception.InnerException}");
            }, TaskContinuationOptions.NotOnRanToCompletion);
            t.Wait();
        }

        void ClientReconnect(IPEndPoint ep, int clientId)
        {
            _logUtility.Logging($"2. GameUdpServer：客户端（{ep}）断线重连……");
            _semaphore.Wait();
            int frames = _model.GetClientHandledFrames(clientId);
            _need2ReissueClients.Add(ep, frames);
            _semaphore.Release();
            _logUtility.Logging($"2. GameUdpServer：客户端（{ep}）断线重连处理完毕，等待下一帧发送");
        }
        #endregion

        #region 方法实现
        protected override void OnPlayerTryJoiningGame(IPEndPoint ep, TryJoinningGameEvent e)
        {
            _logUtility.Logging($"1. GameUdpServer：接收到客户端（{ep}）尝试连接……");
            var msg = new NetworkEventToSend()
            {
                eventType = NetworkEventType.TryJoiningGame,
                tryJoinningGameEvent = new TryJoinningGameEvent() { errorType = JoinGameErrorType.None }, 
            };
            _semaphore.Wait();
            // 人满了且之前未连接过
            if (_connectedClients.Count >= _maxPlayersCount && _model.GetClientHandledFrames(e.playerId) == 0)
                msg.tryJoinningGameEvent.errorType = JoinGameErrorType.NotAvailable;
            // 人没满则回应，并附加其他玩家 ID
            else
            {
                msg.tryJoinningGameEvent.otherPlayers = new List<int>(_connectedClients.Count);
                msg.tryJoinningGameEvent.otherPlayersName = new List<string>(_connectedClients.Count);
                foreach (var data in _connectedClients.Values)
                {
                    if (data.id == e.playerId || data.isDisconnected)
                        continue;
                    msg.tryJoinningGameEvent.otherPlayers.Add(data.id);
                    msg.tryJoinningGameEvent.otherPlayersName.Add(data.name);
                }
            }
            var buffer = _jsonUtility.GetBytesFromObjectWithUTF8(msg);
            try
            {
                _udpClient.SendAsync(buffer, buffer.Length, ep);
            }
            catch (SocketException ex)
            {
                _logUtility.Logging($"1. GameUdpServer：回应该客户端（{ep}）的尝试连接出错：" +
                    $"{Environment.NewLine}{ex}");
            }
            finally
            {
                _semaphore.Release();
            }
            _logUtility.Logging($"1. GameUdpServer：回应该客户端（{ep}）的尝试连接完毕");
        }

        protected override void OnPlayerJoinedGame(IPEndPoint ep, JoinGameEvent e)
        {
            _logUtility.Logging($"2. GameUdpServer：客户端（{ep}）加入游戏……");
            if (_connectedClients.ContainsKey(e.playerId))
            {
                ClientReconnect(ep, e.playerId);
                return;
            }
            _semaphore.Wait();
            // 向字典添加数据
            _connectedClients.Add(e.playerId, new PlayerData()
            {
                id = e.playerId,
                name = e.playerName,
                isDisconnected = false,
                ep = ep,
            });
            _clientsToCheck.Add(ep);
            _model.AddClient(e.playerId);
            bool flag = _model.GetClientHandledFrames(e.playerId) != _model.CurFrame;
            if (flag)
                _need2ReissueClients.Add(ep, 0);
            _semaphore.Release();
            _logUtility.Logging($"2. GameUdpServer：处理客户端（{ep}）的加入完毕");
        }

        protected override void OnClientSendInputs(IPEndPoint ep, ClientInputsEvent e)
        {
            //_logUtility.Logging($"1. GameUdpServer：接收到客户端（{ep}）的输入……");
            // 如果客户端发来的已处理帧数是与服务器一致的，那么就更新数据
            if (e.clientFrame == _model.CurFrame)
                _model.AddClientOptions(e);
            // 否则就记录该客户端需要补发 
            else if (e.clientFrame < _model.CurFrame)
            {
                _semaphore.Wait();
                _need2ReissueClients.TryAdd(ep, e.clientFrame);
                _semaphore.Release();
            }
        }

        protected override void OnServerDispatchedInputs(ServerDispathesInputsEvent e)
        {
            throw new NotImplementedException();
        }

        protected override void OnServerReissuedInputs(ServerReissuesFramesEvent e)
        {
            throw new NotImplementedException();
        }

        protected override void OnNetworkNotDisconnect(IPEndPoint ep)
        {
            if (_checkDisconnectionMsgBuffer == null)
            {
                _checkDisconnectionMsgBuffer = _jsonUtility.GetBytesFromObjectWithUTF8(new NetworkEventToSend()
                {
                    eventType = NetworkEventType.NetworkDoesNotDisconnected
                });
            }
            _semaphore.Wait();
            _clientsToCheck.Add(ep);
            // 告诉该客户端没有断连
            _udpClient.SendAsync(_checkDisconnectionMsgBuffer, _checkDisconnectionMsgBuffer.Length, ep);
            _semaphore.Release();
        }

        protected override void CheckDisconnection()
        {
            _semaphore.Wait();
            foreach (var client in _connectedClients)
            {
                bool flag = _clientsToCheck.Contains(client.Value.ep);
                client.Value.isDisconnected = !flag;
            }
            _clientsToCheck.Clear();
            _semaphore.Release();
        }

        public override void OnClose()
        {
            _connectedClients?.Clear();
            _semaphore?.Release();
            _semaphore?.Dispose();
            _semaphore = null;
            _connectedClients = null;
        }

        #endregion

        #region 数据结构类
        class PlayerData
        {
            public int id;
            public string name;
            public bool isDisconnected;
            public IPEndPoint ep;
        }
        #endregion
    }
}
